/*
 * Copyright 2007 Michał Baliński
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package pl.balon.gwt.diagrams.client.connection;

import java.util.List;

import pl.balon.gwt.diagrams.client.common.bezier.BezierCurve;
import pl.balon.gwt.diagrams.client.connection.calculator.BezierConnectionCalculator;
import pl.balon.gwt.diagrams.client.connection.calculator.ConnectionDataCalculator;
import pl.balon.gwt.diagrams.client.connection.data.ConnectionData;
import pl.balon.gwt.diagrams.client.connection.data.Point;
import pl.balon.gwt.diagrams.client.connection.pointwidget.MoveablePointWidget;
import pl.balon.gwt.diagrams.client.connection.pointwidget.MoveablePointWidgetMoveEvent;
import pl.balon.gwt.diagrams.client.connector.Connector;
import pl.balon.gwt.diagrams.client.connector.Direction;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;

/**
 * Bezier connection widget.
 *
 * @author Michał Baliński (michal.balinski@gmail.com)
 * @author Alex Pinheiro das Graças(Alex.pgracas@gmail.com)
 */
public class BezierTwoEndedConnection extends AbstractTwoEndedDraggableConnection{

	static final String type = "BEZIER";

	@Override
	public String getType() {
		return type;
	}
	/**
	 * Represents bezier curve. Implementation depends on browser type.
	 * Instantiated through deferred binding
	 */
	protected BezierCurve curve = (BezierCurve) GWT.create(BezierCurve.class);

	/**
	 * Constructs bezier connection
	 */
	public BezierTwoEndedConnection(Connector[] toConnect) {
		super(toConnect);
		setElement(curve.getElement());
		addStyleName("gwt-diagrams-connection");
	}

	@Override
	public Element getElement() {
		return curve.getElement();
	}

	/**
	 * Constructs bezier connection
	 */
	public BezierTwoEndedConnection(Connector c1, Connector c2) {
		this(new Connector[]{c1, c2});
	}

	/**
	 * @see pl.balon.gwt.diagrams.client.connection.AbstractConnection#createCalculator()
	 */
	@Override
	protected ConnectionDataCalculator createCalculator() {
		return new BezierConnectionCalculator();
	}

	/**
	 * @see pl.balon.gwt.diagrams.client.connection.AbstractConnection#update(pl.balon.gwt.diagrams.client.connection.data.ConnectionData)
	 */
	@Override
	public void update(ConnectionData data) {
		if( data.getPoints().size() != 4 ){
			throw new IllegalArgumentException("Expected two connection points");
		}

		if(this.movablePoints.size()!=2 && isShowMoveablePoints()) {
			this.movablePoints.add(createMovableWidget());
			this.movablePoints.add(createMovableWidget());
			for(int i=0;i<2;i++) {
				MoveablePointWidget w = this.movablePoints.get(i);
				Panel panel = (Panel) this.getParent();
				panel.add((Widget) w);
				w.setControledIndexPoint(i);
			}
		}

		curve.draw(
				data.getPoints().get(0),
				data.getPoints().get(1),
				data.getPoints().get(2),
				data.getPoints().get(3));

		// Endings
		if( getEnding(0)!=null ){
			Point p = data.getPoints().get(0);
			Point c = data.getPoints().get(2);

			if( p.left > c.left ){ // RIGHT
				getEnding(0).update(p.left, p.top, Direction.RIGHT.getAngle());
			} else if( p.left < c.left ){ // LEFT
				getEnding(0).update(p.left, p.top, Direction.LEFT.getAngle());
			} else if( p.top > c.top ){ // DOWN
				getEnding(0).update(p.left, p.top, Direction.DOWN.getAngle());
			} else if( p.top < c.top ){ // UP
				getEnding(0).update(p.left, p.top, Direction.UP.getAngle());
			}

		}

		if( getEnding(1)!=null ){
			Point p = data.getPoints().get(1);
			Point c = data.getPoints().get(3);

			if( p.left > c.left ){ // RIGHT
				getEnding(1).update(p.left, p.top, Direction.RIGHT.getAngle());
			} else if( p.left < c.left ){ // LEFT
				getEnding(1).update(p.left, p.top, Direction.LEFT.getAngle());
			} else if( p.top > c.top ){ // DOWN
				getEnding(1).update(p.left, p.top, Direction.DOWN.getAngle());
			} else if( p.top < c.top ){ // UP
				getEnding(1).update(p.left, p.top, Direction.UP.getAngle());
			}

		}

	}

	/**
	 * @see pl.balon.gwt.diagrams.client.connection.AbstractConnection#remove()
	 */
	@Override
	public void remove() {
		curve.remove();
		super.remove();
	}


	private Point getBezierPoint(Point p1, Point p2, Point c1, Point c2, double percent) {
		double left=0;
		left=Math.pow((1-percent),3)*p1.left+3*Math.pow((1-percent), 2)*percent*c1.left+3*(1-percent)*Math.pow(percent,2)*c2.left+Math.pow(percent,3)*p2.left;
		double top=0;
		top=Math.pow((1-percent),3)*p1.top+3*Math.pow((1-percent), 2)*percent*c1.top+3*(1-percent)*Math.pow(percent,2)*c2.top+Math.pow(percent,3)*p2.top;
		return new Point((int)Math.round(left),(int)Math.round(top));
	}

	@Override
	public void movablePointPositions(ConnectionData data) {
		List<Point> points = data.getPoints();
		MoveablePointWidget m = this.movablePoints.get(0);
		Point p1 = this.getBezierPoint(points.get(0), points.get(1), points.get(2),points.get(3), 0.33);
		m.setPosition(p1.left, p1.top);
		m = this.movablePoints.get(1);
		Point p2 = this.getBezierPoint(points.get(0), points.get(1), points.get(2),points.get(3), 0.66);
		m.setPosition(p2.left, p2.top);
	}

	@Override
	public void onMovePoint(MoveablePointWidgetMoveEvent event) {
			GWT.log("Widget Moved", null);

			ConnectionData bdata =  data;
			Point p1 = bdata.getPoints().get(0);
			Point p2 = bdata.getPoints().get(1);
			Point c1 = bdata.getPoints().get(2);
			Point c2 = bdata.getPoints().get(3);

			MoveablePointWidget widget = event.getMovePointAction();
			int changed = widget.getControledIndexPoint();
			double left=0, top=0, percent=0;

			if(changed==0) {
				percent=0.33;
				left=-(Math.pow((1-percent),3)*p1.left-widget.getLeft()+3*(1-percent)*Math.pow(percent,2)*c2.left+Math.pow(percent,3)*p2.left)/(3*Math.pow((1-percent), 2)*percent);
				top=-(Math.pow((1-percent),3)*p1.top-widget.getTop()+3*(1-percent)*Math.pow(percent,2)*c2.top+Math.pow(percent,3)*p2.top)/(3*Math.pow((1-percent), 2)*percent);
				c1.left=(int) Math.round(left);
				c1.top=(int) Math.round(top);
			}else if(changed==1) {
				percent=0.66;
				left=-(Math.pow((1-percent),3)*p1.left+3*Math.pow((1-percent), 2)*percent*c1.left-widget.getLeft()+Math.pow(percent,3)*p2.left)/(3*(1-percent)*Math.pow(percent,2));
				top=-(Math.pow((1-percent),3)*p1.top+3*Math.pow((1-percent), 2)*percent*c1.top-widget.getTop()+Math.pow(percent,3)*p2.top)/(3*(1-percent)*Math.pow(percent,2));
				c2.left=(int) Math.round(left);
				c2.top=(int) Math.round(top);
			}
			this.update(data);
			movablePointPositions(this.data);
	}

	@Override
	public Point getCenter() {
		return getBezierPoint(data.getPoints().get(0),data.getPoints().get(1), data.getPoints().get(2), data.getPoints().get(3), 0.5);
	}
}
